package com.ucode.springboot.starter.security.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.BeanIds;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.logout.HttpStatusReturningLogoutSuccessHandler;
import com.ucode.springboot.starter.security.entity.UcodeUser;
import com.ucode.springboot.starter.security.filter.UcodeAuthenticationTokenFilter;
import com.ucode.springboot.starter.security.handler.RestAuthenticationExceptionHandler;
import com.ucode.springboot.starter.security.handler.RestfulAccessDeniedHandler;
import com.ucode.springboot.starter.security.provider.UcodePasswordEncoder;
import com.ucode.springboot.starter.security.service.UcodePasswordVerifier;
import com.ucode.springboot.starter.security.service.UcodeSecurityOperationService;
import com.ucode.springboot.starter.security.service.UcodeUserService;

/**
 * spring Security 安全配置类
 * @author: liliang
 * @date: 2019年12月19日 下午7:47:42
 */
@Configuration
@EnableWebSecurity//1: 加载WebSecurityConfiguration配置类, 配置安全认证策略。2: 加载AuthenticationConfiguration, 配置认证信息
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class UcodeSecurityConfig extends WebSecurityConfigurerAdapter{

    @Autowired
    private UserDetailsService userDetailsService; //具体实现UcodeUserDetailsService
    @Autowired
    private UcodePasswordEncoder passwordEncoder;
    @Autowired
    private UcodeSecurityOperationService securityOperationService;
    
    @Autowired
    private RestfulAccessDeniedHandler restfulAccessDeniedHandler;//权限不足处理类 
    @Autowired
    private RestAuthenticationExceptionHandler restAuthenticationExceptionHandler;//其他异常处理类
    @Autowired
    private UcodeAuthenticationTokenFilter ucodeAuthenticationTokenFilter;
    
    @Bean
    @ConditionalOnMissingBean
    public UcodeUserService defaultUcodeUserService(){
        UcodeUserService ucodeUserService = new UcodeUserService() {
            @Override
            public UcodeUser getUser(String username) throws RuntimeException {
                throw new RuntimeException("not found UcodeUserService implementation !");
            }
        };
        return ucodeUserService;
    }
    
    @Bean
    @ConditionalOnMissingBean
    public UcodePasswordVerifier defaultUcodePasswordVerifier(){
        UcodePasswordVerifier passwordVerifier = new UcodePasswordVerifier() {
            @Override
            public boolean matches(CharSequence rawPassword, String encodedPassword) {
                throw new RuntimeException("not found UcodePasswordVerifier implementation !");
            }
        };
        return passwordVerifier;
    }
    
    
    /**
     * 登录身份认证组件
     */
    @Override
    public void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService) // 设置UserDetailsService 获取user对象
            .passwordEncoder(passwordEncoder);//密码验证
        
    }
    
    
    /**
     * 设置认证请求,访问路径URL的授权策略
     * 例如登录、Swagger访问免登录认证等
     */
    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception {
        httpSecurity.cors().and().csrf().disable()//禁用 csrf, 由于使用的是JWT，我们这里不需要csrf
        .headers().frameOptions()// 允许iframe内呈现
        .sameOrigin().and()
        .sessionManagement()// 基于token定制session 策略，所以不需要原始的session策略
        .sessionCreationPolicy(SessionCreationPolicy.STATELESS)//调整为让 Spring Security不创建和使用 session
        .and()
        .authorizeRequests()
        //注意：antMatchers的顺序->范围小的放在前面，范围大的放在后面，反之范围小的就不会匹配，按照范围大的处理
        .antMatchers(HttpMethod.GET, //允许对于网站静态资源的无授权访问
//                "/",
//                "/*.html",
//                "/favicon.ico",
//                "/**/*.html",
//                "/**/*.css",
//                "/**/*.js",
//                "/swagger-resources/**",
//                "/v2/api-docs/**"
                securityOperationService.getIgnoreGetUrls()
        ).permitAll()
        .antMatchers(
//                "/admin/login", "/admin/register"
                securityOperationService.getAnonymousUrls()
                ).permitAll()//登录注册要允许匿名访问
        .antMatchers(HttpMethod.OPTIONS).permitAll()//跨域请求会先进行一次options请求,设置不拦截OPTIONS请求
        .anyRequest()// 其他所有请求需要身份鉴权认证
        .authenticated();
        // 禁用缓存
        httpSecurity.headers().cacheControl();
        // 退出登录处理器
        httpSecurity.logout().logoutSuccessHandler(new HttpStatusReturningLogoutSuccessHandler());
        
        // 添加JWT filter
        httpSecurity.addFilterBefore(ucodeAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class)
                // 添加权限不足 filter
                .exceptionHandling().accessDeniedHandler(restfulAccessDeniedHandler)
                //其他异常处理类
                .authenticationEntryPoint(restAuthenticationExceptionHandler);
    }
    
    /**
     * 用户身份认证管理器->负责对用户凭证进行认证
     */
    @Bean(name = BeanIds.AUTHENTICATION_MANAGER)
    @Override
    public AuthenticationManager authenticationManager() throws Exception {
        return super.authenticationManager();
    }
}
